<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <meta name="description" content="Create 3D models using glTF." />
    <meta name="cesium-sandcastle-labels" content="3D Tiles Next" />
    <title>Cesium Demo</title>
    <script type="text/javascript" src="../Sandcastle-header.js"></script>
    <script
      type="text/javascript"
      src="../../../Build/CesiumUnminified/Cesium.js"
      nomodule
    ></script>
    <script type="module" src="../load-cesium-es6.js"></script>
  </head>
  <body
    class="sandcastle-loading"
    data-sandcastle-bucket="bucket-requirejs.html"
  >
    <style>
      @import url(../templates/bucket.css);
      #toolbar {
        background: rgba(42, 42, 42, 0.8);
        padding: 4px;
        border-radius: 4px;
      }
      #toolbar input {
        vertical-align: middle;
        padding-top: 2px;
        padding-bottom: 2px;
      }
    </style>
    <div id="cesiumContainer" class="fullSize"></div>
    <div id="loadingOverlay"><h1>Loading...</h1></div>
    <div id="toolbar"></div>
    <script id="cesium_sandcastle_script">
      function startup(Cesium) {
        "use strict";
        //Sandcastle_Begin
        Cesium.ExperimentalFeatures.enableModelExperimental = true;
        const viewer = new Cesium.Viewer("cesiumContainer", {
          orderIndependentTranslucency: false,
        });

        viewer.clock.currentTime = Cesium.JulianDate.fromIso8601(
          "2021-11-09T20:27:37.016064475348684937Z"
        );

        // Model positioning ===============================================

        const position = Cesium.Cartesian3.fromDegrees(
          -123.0744619,
          44.0503706,
          0
        );
        const hpr = new Cesium.HeadingPitchRoll(0, 0, 0);
        const fixedFrameTransform = Cesium.Transforms.localFrameToFixedFrameGenerator(
          "north",
          "west"
        );

        // Custom Shader Definitions ========================================

        // Dragging the mouse will expand/shrink the model.
        const expandModelShader = new Cesium.CustomShader({
          uniforms: {
            // Vector from latest drag center to the mouse
            u_drag: {
              type: Cesium.UniformType.VEC2,
              value: new Cesium.Cartesian2(0.0, 0.0),
            },
          },
          vertexShaderText: `
            // If the mouse is dragged to the right, the model grows
            // If the mouse is dragged to the left, the model shrinks
            void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput)
            {
                vsOutput.positionMC += 0.01 * u_drag.x * vsInput.attributes.normalMC;
            }
            `,
        });

        const textureUniformShader = new Cesium.CustomShader({
          uniforms: {
            // elapsed time in seconds for animation
            u_time: {
              type: Cesium.UniformType.FLOAT,
              value: 0,
            },
            // user-defined texture
            u_stripes: {
              type: Cesium.UniformType.SAMPLER_2D,
              value: new Cesium.TextureUniform({
                url: "../../SampleData/cesium_stripes.png",
              }),
            },
          },
          // Apply the texture to the model, but move the texture coordinates
          // a bit over time so it's animated.
          fragmentShaderText: `
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
            {
                vec2 texCoord = fsInput.attributes.texCoord_0 + 0.1 * vec2(u_time, 0.0);
                material.diffuse = texture2D(u_stripes, texCoord).rgb;
            }
            `,
        });

        // make a checkerboard texture with an alpha that increases with the
        // diagonal number
        function makeCheckerboardTexture(textureSize) {
          const checkerboard = new Uint8Array(4 * textureSize * textureSize);

          const maxDiagonal = 2 * (textureSize - 1);
          for (let i = 0; i < textureSize; i++) {
            for (let j = 0; j < textureSize; j++) {
              const index = i * textureSize + j;
              // Checking the parity of the diagonal number gives a checkerboard
              // pattern.
              const diagonal = i + j;
              if (diagonal % 2 === 0) {
                // set the square red. We only need to set the red channel!
                checkerboard[4 * index] = 255;
              }
              // otherwise we'd set the square to black. But arrays are already
              // initialized to 0s so nothing needed here.

              // for the alpha channel, map the diagonal number to [0, 255]
              checkerboard[4 * index + 3] = (255 * diagonal) / maxDiagonal;
            }
          }
          return new Cesium.TextureUniform({
            typedArray: checkerboard,
            width: textureSize,
            height: textureSize,
            // Don't interpolate, we want crisp checkerboard edges
            minificationFilter: Cesium.TextureMinificationFilter.NEAREST,
            magnificationFilter: Cesium.TextureMagnificationFilter.NEAREST,
          });
        }
        const checkerboardTexture = makeCheckerboardTexture(8);

        // Use the checkerboard red channel as a mask
        const checkerboardMaskShader = new Cesium.CustomShader({
          uniforms: {
            u_checkerboard: {
              type: Cesium.UniformType.SAMPLER_2D,
              value: checkerboardTexture,
            },
          },
          fragmentShaderText: `  
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
            {
                vec2 texCoord = fsInput.attributes.texCoord_0;
                vec4 checkerboard = texture2D(u_checkerboard, texCoord);
                material.diffuse = mix(material.diffuse, vec3(0.0), checkerboard.r);
            }
            `,
        });

        // Color like a checkerboard but make the transparency vary with
        // the diagonal
        const checkerboardAlphaShader = new Cesium.CustomShader({
          uniforms: {
            u_checkerboard: {
              type: Cesium.UniformType.SAMPLER_2D,
              value: checkerboardTexture,
            },
          },
          // necessary when setting material.alpha
          isTranslucent: true,
          fragmentShaderText: `
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
            {
                vec2 texCoord = fsInput.attributes.texCoord_0;
                vec4 checkerboard = texture2D(u_checkerboard, texCoord);
                material.diffuse = checkerboard.rgb;
                material.alpha = checkerboard.a;
            }
            `,
        });

        // Use the checkerboard to cut holes in the model
        const checkerboardHolesShader = new Cesium.CustomShader({
          uniforms: {
            u_checkerboard: {
              type: Cesium.UniformType.SAMPLER_2D,
              value: checkerboardTexture,
            },
          },
          fragmentShaderText: `
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
            {
                vec2 texCoord = fsInput.attributes.texCoord_0;
                vec4 checkerboard = texture2D(u_checkerboard, texCoord);
                if (checkerboard.r > 0.0) {
                    discard;
                }
            }
            `,
        });

        // This example is to demonstrate the conventions used for orienting
        // the texture. +x is to the right and +y is from **bottom to top**.
        // This is to be consistent with WebGL conventions.
        //
        // This example also demonstrates how to use a different pixel format,
        // in this case, RGB.
        function makeGradientTexture() {
          const size = 256;
          const typedArray = new Uint8Array(3 * size * size);
          for (let i = 0; i < size; i++) {
            for (let j = 0; j < size; j++) {
              const index = i * size + j;
              // red increases in the +x direction (to the right)
              typedArray[3 * index + 0] = j;
              // Green increases in the +y direction (from bottom to top)
              typedArray[3 * index + 1] = i;
              // blue is 0 so it is omitted.
            }
          }

          return new Cesium.TextureUniform({
            typedArray: typedArray,
            width: size,
            height: size,
            pixelFormat: Cesium.PixelFormat.RGB,
          });
        }
        const gradientTexture = makeGradientTexture();

        // Color the texture along its UV coordinates.
        const gradientShader = new Cesium.CustomShader({
          uniforms: {
            u_gradient: {
              type: Cesium.UniformType.SAMPLER_2D,
              value: gradientTexture,
            },
          },
          fragmentShaderText: `
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
            {
                material.diffuse = texture2D(u_gradient, fsInput.attributes.texCoord_0).rgb;
            }
            `,
        });

        // Dragging the mouse will modify the PBR values
        const modifyPbrShader = new Cesium.CustomShader({
          uniforms: {
            // Vector from latest drag center to the mouse
            u_drag: {
              type: Cesium.UniformType.VEC2,
              value: new Cesium.Cartesian2(0.0, 0.0),
            },
          },
          fragmentShaderText: `
            // Click and drag to vary the PBR values
            void fragmentMain(FragmentInput vsInput, inout czm_modelMaterial material)
            {
                float dragDistance = length(u_drag);
                float variation = smoothstep(0.0, 300.0, dragDistance);
            // variation adds an golden tint to the specular highlights
                material.specular = mix(material.specular, vec3(0.8, 0.5, 0.1), variation);
            // variation makes the material glossier
                material.roughness = clamp(1.0 - variation, 0.01, 1.0);
            // variation mixes some red into the diffuse color
                material.diffuse += vec3(0.5, 0.0, 0.0) * variation;
            }
            `,
        });

        const pointCloudWaveShader = new Cesium.CustomShader({
          uniforms: {
            // elapsed time in seconds for animation
            u_time: {
              type: Cesium.UniformType.FLOAT,
              value: 0,
            },
          },
          vertexShaderText: `
            void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput)
            {
            // This model's x and y coordinates are in the range [0, 1], which
            // conveniently doubles as UV coordinates.
                vec2 uv = vsInput.attributes.positionMC.xy;
            // Make the point cloud undulate in a complex wave that varies in
            // both space and time. The amplitude is based on the original shape
            // of the point cloud (which already is a wavy surface). The wave
            // is computed relative to the center of the model, hence the
            // transformations from [0, 1] -> [-1, 1] -> [0, 1]
                float amplitude = 2.0 * vsInput.attributes.positionMC.z - 1.0;
                float wave = amplitude * sin(2.0 * czm_pi * uv.x - 2.0 * u_time) * sin(u_time);
                vsOutput.positionMC.z = 0.5 + 0.5 * wave;
            // Make the points pulse in and out by changing their size
                vsOutput.pointSize = 10.0 + 5.0 * sin(u_time);
            }
            `,
          fragmentShaderText: `
            void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
            {
            // Make the points circular instead of square
                float distance = length(gl_PointCoord - 0.5);
                if (distance > 0.5) {
                    discard;
                }
            // Make a sinusoid color palette that moves in the general direction
            // of the wave, but at a different speed.
            // Coefficients were chosen arbitrarily
                vec2 uv = fsInput.attributes.positionMC.xy;
                material.diffuse = 0.2 * fsInput.attributes.color_0.rgb;
                material.diffuse += vec3(0.2, 0.3, 0.4) + vec3(0.2, 0.3, 0.4) * sin(2.0 * czm_pi * vec3(3.0, 2.0, 1.0) * uv.x - 3.0 * u_time);
            }
            `,
        });

        // Demos ==============================================================

        const models = {
          balloon: "../../SampleData/models/CesiumBalloon/CesiumBalloon.glb",
          drone: "../../SampleData/models/CesiumDrone/CesiumDrone.glb",
          pawns: "../../SampleData/models/CesiumDrone/Pawns.glb",
          milkTruck:
            "../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb",
          groundVehicle:
            "../../SampleData/models/GroundVehicle/GroundVehicle.glb",
          pointCloudWave:
            "../../SampleData/models/PointCloudWave/PointCloudWave.glb",
        };

        let needsDrag = false;
        const demos = [
          {
            text: "Custom Texture",
            onselect: function () {
              selectModel(models.groundVehicle, textureUniformShader);
              needsDrag = false;
            },
          },
          {
            text: "Procedural Texture",
            onselect: function () {
              selectModel(models.balloon, checkerboardMaskShader);
              needsDrag = false;
            },
          },
          {
            text: "Translucent materials",
            onselect: function () {
              selectModel(models.balloon, checkerboardAlphaShader);
              needsDrag = false;
            },
          },
          {
            text: "Use Texture as Mask",
            onselect: function () {
              selectModel(models.balloon, checkerboardHolesShader);
              needsDrag = false;
            },
          },
          {
            text: "Procedural Gradient Texture",
            onselect: function () {
              selectModel(models.balloon, gradientShader);
              needsDrag = false;
            },
          },
          {
            text: "Modify PBR values via Mouse Drag",
            onselect: function () {
              selectModel(models.groundVehicle, modifyPbrShader);
              needsDrag = true;
            },
          },
          {
            text: "Expand Model via Mouse Drag",
            onselect: function () {
              selectModel(models.milkTruck, expandModelShader);
              needsDrag = true;
            },
          },
          {
            text: "Animated Point Cloud",
            onselect: function () {
              selectModel(models.pointCloudWave, pointCloudWaveShader);
              needsDrag = false;
            },
          },
        ];

        function selectModel(url, customShader) {
          viewer.scene.primitives.removeAll();
          const model = viewer.scene.primitives.add(
            Cesium.ModelExperimental.fromGltf({
              gltf: url,
              customShader: customShader,
              modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame(
                position,
                hpr,
                Cesium.Ellipsoid.WGS84,
                fixedFrameTransform
              ),
            })
          );

          model.readyPromise.then(function (model) {
            viewer.camera.flyToBoundingSphere(model.boundingSphere, {
              duration: 0.5,
            });
          });
        }
        Sandcastle.addToolbarMenu(demos);

        // Event handlers =====================================================

        const startTime = performance.now();
        viewer.scene.postUpdate.addEventListener(function () {
          const elapsedTimeSeconds = (performance.now() - startTime) / 1000;
          textureUniformShader.setUniform("u_time", elapsedTimeSeconds);
          pointCloudWaveShader.setUniform("u_time", elapsedTimeSeconds);
        });

        let dragActive = false;
        const dragCenter = new Cesium.Cartesian2();

        viewer.screenSpaceEventHandler.setInputAction(function (movement) {
          if (!needsDrag) {
            return;
          }

          const pickedFeature = viewer.scene.pick(movement.position);
          if (!Cesium.defined(pickedFeature)) {
            return;
          }

          viewer.scene.screenSpaceCameraController.enableInputs = false;

          // set the new drag center
          dragActive = true;
          movement.position.clone(dragCenter);
        }, Cesium.ScreenSpaceEventType.LEFT_DOWN);

        const scratchDrag = new Cesium.Cartesian2();
        viewer.screenSpaceEventHandler.setInputAction(function (movement) {
          if (!needsDrag) {
            return;
          }

          if (dragActive) {
            // get the mouse position relative to the center of the screen
            const drag = Cesium.Cartesian3.subtract(
              movement.endPosition,
              dragCenter,
              scratchDrag
            );

            // Update uniforms
            expandModelShader.setUniform("u_drag", drag);
            modifyPbrShader.setUniform("u_drag", drag);
          }
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

        viewer.screenSpaceEventHandler.setInputAction(function (movement) {
          if (!needsDrag) {
            return;
          }

          viewer.scene.screenSpaceCameraController.enableInputs = true;

          dragActive = false;
        }, Cesium.ScreenSpaceEventType.LEFT_UP);

        //Sandcastle_End
        Sandcastle.finishedLoading();
      }
      if (typeof Cesium !== "undefined") {
        window.startupCalled = true;
        startup(Cesium);
      }
    </script>
  </body>
</html>
